﻿// *---------------------------------------------------------------------------------------------------------*
// Solution:         JwikIDE									
// Project:           JwiKIDE								
// Source File:    ConstantPool.cs									
// *---------------------------------------------------------------------------------------------------------*
// 
// © 2012 Digital Six Labs, LLC All Rights Reserved
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software 
// and associated documentation files (the "Software"), to deal in the Software without restriction, 
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
// subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or substantial 
// portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 
// LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// 
// 
// 
// 
namespace JwiKIDE.JavaClassFileClasses
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Text;

    public enum ConstantTypes : sbyte
    {
        Unused = -1,
        UTF=1,
        Unicode,
        Int,
        Float,
        Long,
        Double,
        Class,
        String,
        FieldRef,
        MethodRef,
        InterfaceMethodRef,
        NameAndType
    }

    public class ConstantPoolEntry
    {
        public ConstantTypes TypeCode { get; set; }

        public Int32 IntValue { get; set; }

        public String StringValue { get; set; }

        public Int64 LongValue { get; set; }

        public float FloatValue { get; set; }

        public double DoubleValue { get; set; }

        public Int32 Index1 { get; set; }

        public Int32 Index2 { get; set; }

        internal void ReadFromFile(BinaryReader br)
        {
            var tag = (ConstantTypes)br.ReadByte();
            switch(tag)
            {
                case ConstantTypes.UTF:
                    var len1 = br.ReadInt16();
                    TypeCode = ConstantTypes.UTF;
                    var charArray1 = br.ReadChars(len1);
                    StringValue = new string(charArray1);
                    TypeCode = ConstantTypes.Unicode;
                    break;
                case ConstantTypes.Unicode:
                    var len = br.ReadInt16();
                    var charArray = br.ReadChars(len);
                    StringValue = new string(charArray);
                    TypeCode = ConstantTypes.Unicode;
                    break;
                case ConstantTypes.Int:
                    TypeCode = ConstantTypes.Int;
                    IntValue = br.ReadInt32();
                    break;
                case ConstantTypes.Float:
                    throw new IOException("Floating point values are not supported at this time.");
                   // TypeCode = ConstantTypes.Float;
                   // FloatValue = br.ReadSingle();
                    break;
                case ConstantTypes.Long:
                    TypeCode = ConstantTypes.Long;
                    LongValue = br.ReadInt64();
                    break;
                case ConstantTypes.Double:
                    throw new IOException("Double values are not supported at this time.");
                    //TypeCode = ConstantTypes.Double;
                    //DoubleValue = br.ReadDouble();
                    break;
                case ConstantTypes.Class:
                    TypeCode = ConstantTypes.Class;
                    Index1 = br.ReadInt16();
                    break;
                case ConstantTypes.String:
                    TypeCode = ConstantTypes.String;
                    Index1 = br.ReadInt16();
                    break;
                case ConstantTypes.FieldRef:
                    TypeCode = ConstantTypes.FieldRef;
                    Index1 = br.ReadInt16();
                    Index2 = br.ReadInt16();
                    break;
                case ConstantTypes.MethodRef:
                    TypeCode = ConstantTypes.MethodRef;
                    Index1 = br.ReadInt16();
                    Index2 = br.ReadInt16();
                    break;
                case ConstantTypes.InterfaceMethodRef:
                    TypeCode = ConstantTypes.InterfaceMethodRef;
                    Index1 = br.ReadInt16();
                    Index2 = br.ReadInt16();
                    break;
                case ConstantTypes.NameAndType:
                    TypeCode = ConstantTypes.NameAndType;
                    Index1 = br.ReadInt16();
                    Index2 = br.ReadInt16();
                    break;
                default:
                    throw new IOException("Unknown constant type.  Unrecognized type = "+tag);
            }
        }
    }

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public class ConstantPool
    {
        public List<ConstantPoolEntry> ConstantPoolEntries { get; private set; }

        public ConstantPool()
        {
            ConstantPoolEntries = new List<ConstantPoolEntry>();
        }

        public void ReadFromFile(BinaryReader br)
        {
            int count = br.ReadInt16();
            // first entry is always unused
            ConstantPoolEntries.Add(new ConstantPoolEntry {TypeCode = ConstantTypes.Unused});
            // i starts at 1 because we skip the first entry
            for(var i=1; i<count; i++)
            {
                var newEntry = new ConstantPoolEntry();

                newEntry.ReadFromFile(br);

                ConstantPoolEntries.Add(newEntry);
                // double and long constants take up two slots in the file, so advance the pointer 
                // for those types
                if (newEntry.TypeCode == ConstantTypes.Double
                    || newEntry.TypeCode == ConstantTypes.Long) i++;
            }
        }
        public bool IsNativeMethod(string className, string methodName, string methodType)
        {
            foreach (var file in ConfigFile.NativeFiles)
            {
                foreach (var method in file.NativeMethods)
                {
                    if (className == method.ClassName
                        && methodName == method.MethodName
                        && methodType == method.MethodType)
                    {
                        return true;
                    }
                }
            }
            return false;
        }
        public bool MethodExists(string className, string methodName, string methodType)
        {
            foreach(var cl in ClassLoader.Classes)
            {
                foreach(var method in cl.Methods)
                {
                    if(cl.ClassName == className
                        && method.Name == methodName)
                    {
                        if (methodType == null) return true;
                        if (methodType == method.Signature) return true;
                    }
                }
                if(className==cl.ClassName) 
                    throw new IOException("Found the class, but the method wasn't there: "+className+"."+methodName+":"+methodType);
            }
            return false;
        }
        public void ResolveMethodReferences()
        {
            for(var i=0; i<ConstantPoolEntries.Count;i++)
            {
                var cpe = ConstantPoolEntries[i];
                if(cpe.TypeCode == ConstantTypes.MethodRef)
                {
                    var className = ConstantPoolEntries[ConstantPoolEntries[cpe.Index1].Index1].StringValue;
                    var methodName = ConstantPoolEntries[ConstantPoolEntries[cpe.Index2].Index1].StringValue;
                    var methodType = ConstantPoolEntries[ConstantPoolEntries[cpe.Index2].Index2].StringValue;

                    if(!this.IsNativeMethod(className,methodName,methodType))
                    {
                        if(!this.MethodExists(className,methodName,methodType))
                        {
                            ClassLoader.Load(className+".class");
                        }
                    }

                }
            }
        }

        internal int GetConstantEntry(int index)
        {
            for(int i=0;i<ConstantPoolEntries.Count;i++)
            {
                ConstantPoolEntry ce = ConstantPoolEntries[i];
                if(ce.TypeCode == ConstantTypes.Float)
                {
                    throw new IOException("Float types are not allowed and one was found in the ConstantPool.  Serious bug afoot.");
                }
                if(ce.TypeCode == ConstantTypes.Int)
                {
                    if (index-- == 0) return ce.IntValue;
                }
            }
            return 0;
        }

        public int TotalStrings
        {
            get
            {
                int num = 0;

                for (int i = 0; i < ConstantPoolEntries.Count; i++)
                {
                    ConstantPoolEntry entry = ConstantPoolEntries[i];

                    // check for strings only
                    if (entry.TypeCode == ConstantTypes.String)
                        num++;
                }
                return num;
            }
        }

        internal string GetString(int index)
        {
            for (int i = 0; i < ConstantPoolEntries.Count; i++)
            {
                ConstantPoolEntry entry = ConstantPoolEntries[i];

                // check for strings only
                if (entry.TypeCode == ConstantTypes.String)
                    if (index-- == 0)
                        return ConstantPoolEntries[entry.Index1].StringValue;
            }
            return null;
        }

        internal int ConstantRelocate(int index)
        {
            ConstantPoolEntry entry =ConstantPoolEntries[index];
            int id = 0xffff;

            if (entry.TypeCode ==ConstantTypes.Class)
            {
                String className = ConstantPoolEntries[entry.Index1].StringValue;// getEntryAtIndex(entry.getClassNameIndex()).getString();
                // check if there's a local class with this name
                id = ClassLoader.GetClassIndex(className);//  getClassIndex(className);
                if (id < 0)
                {
                    id = ConfigFile.GetNativeClassId(className);// getNativeClassId(className);
                    if (id < 0)
                    {
                        throw new IOException("Cannot map class reference");
                    }
                }
                id <<= 8; // class id
            }

            else if (entry.TypeCode == ConstantTypes.FieldRef)
            {
                // check if this is a local field
                // try to get the local method index
                if (ClassLoader.FieldExists(ConstantPoolEntries[ConstantPoolEntries[entry.Index1].Index1].StringValue, 
                                            ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index1].StringValue,
                                            ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index2].StringValue))
                {
                    // check if static or not
                    FieldInfo fieldInfo = ClassLoader.GetFieldInfo(ConstantPoolEntries[ConstantPoolEntries[entry.Index1].Index1].StringValue, 
                                            ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index1].StringValue,
                                            ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index2].StringValue);

                    if ((fieldInfo.AccessFlags & (short)AccessTypeFlags.STATIC) != 0)
                    {
                        // index is the number of field in this whole set of classes
                        id = ClassLoader.GetStaticFieldIndex(ConstantPoolEntries[ConstantPoolEntries[entry.Index1].Index1].StringValue, 
                                            ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index1].StringValue,
                                            ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index2].StringValue);

                    }
                    else
                    {
                        // non-static, get access to class it resides in
                        ClassInfo classInfo = ClassLoader.GetClassInfo(GetClassName(entry));

                        // index is the number of non static fields in this local class
                        id = classInfo.GetFieldIndex(0, 
                                            ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index1].StringValue,
                                            ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index2].StringValue);

                        // plus the total number of fields in the super classes	  
                        // walk through all super classes
                        String className = GetClassName(entry);
                        while (ConfigFile.GetNativeClassId(ClassLoader.GetSuperClassName(className)) == -1)
                        {
                            className = ClassLoader.GetSuperClassName(className);
                            id += ClassLoader.GetClassInfo(className).TotalNonStaticFields;
                        }
                    }
                }
                else
                {
                    // we only support static native fields
                    id = ConfigFile.GetFieldId(
                        ConstantPoolEntries[ConstantPoolEntries[entry.Index1].Index1].StringValue,
                        ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index1].StringValue,
                        ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index2].StringValue);

                    if (id == -1)
                    {
                        throw new IOException("Unable to map field reference.");
                    }
                }
            }

            else if (entry.TypeCode == ConstantTypes.String)
            {
                String str = ConstantPoolEntries[entry.Index1].StringValue;  //getEntryAtIndex(entry.getStringIndex()).getString();
                // search for this string
                for (int i = 0; i < ClassLoader.TotalStringCount; i++) 
                    if (ClassLoader.GetString(i)==str) id = i + ClassLoader.TotalConstantCount;

                if (id == 0xffff)
                {
                    throw new IOException("Unable to map string reference");
                }
            }

            else if (entry.TypeCode == ConstantTypes.MethodRef)
            {
                // try to get the local method index
                index = ClassLoader.GetMethodIndex(ConstantPoolEntries[ConstantPoolEntries[entry.Index1].Index1].StringValue, 
                                            ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index1].StringValue,
                                            ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index2].StringValue);

                // didn't work? try native methods
                if (index == -1)
                {
                    id = ConfigFile.GetMethodId(ConstantPoolEntries[ConstantPoolEntries[entry.Index1].Index1].StringValue, 
                                            ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index1].StringValue,
                                            ConstantPoolEntries[ConstantPoolEntries[entry.Index2].Index2].StringValue);

                    if (id == -1)
                    {
                        throw new IOException("Unable to map method reference.");
                    }
                }
                else
                {
                    // new id is just the index of the method in the file
                    id = index;
                }
            }

            else if (entry.TypeCode == ConstantTypes.Float)
            {
                throw new IOException("Floats are not allowed.  We have a serious bug if one made it this far without throwing an exception.  Please find that bug.. it is driving me crazy.");
            }

            else if (entry.TypeCode == ConstantTypes.Int)
            {
                int val = entry.IntValue;
                int ival = val & 0x7fffffff;

                // search for this constant
                for (int i = 0; i < ClassLoader.TotalConstantCount; i++) 
                    if (ClassLoader.GetConstantEntry(i) == ival) 
                        id = i;

                if (id == 0xffff)
                {
                    throw new IOException("Unable to map constant");
                }
            }

            else
            {
                throw new IOException("Not a relocatable type... Yikes.");
            }

            return id;
        }

        private string GetClassName(ConstantPoolEntry entry)
        {
            return ConstantPoolEntries[ConstantPoolEntries[ConstantPoolEntries[entry.Index1].Index1].Index1].StringValue;
        }

        internal int TotalConstantEntries()
        {
            int num = 0;

            for (int i = 0; i < ConstantPoolEntries.Count; i++)
            {
                ConstantPoolEntry entry = ConstantPoolEntries[i];

                // check for ints and floats only
                if (entry.TypeCode == ConstantTypes.Int)
                    num++;
            }
            return num;
        }
    }
}
